﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace LinqToDB.Provider
{
    class SqlNodeVisitor
    {
        int m_intAliasID = 0;
        string GetAliasName()
        {
            return string.Format("T{0}", m_intAliasID++);
        }

        public SqlNode Visit(SqlNode node)
        {
            if (node == null)
                return null;
            switch (node.NodeType)
            {
                case SqlNodeType.Default:
                    return VisitDefault((SqlDefault)node);
                case SqlNodeType.Negate:
                case SqlNodeType.Not:
                case SqlNodeType.Convert:
                    return this.VisitUnary((SqlUnary)node);
                case SqlNodeType.Add:
                case SqlNodeType.Subtract:
                case SqlNodeType.Multiply:
                case SqlNodeType.Divide:
                case SqlNodeType.Modulo:
                case SqlNodeType.And:
                case SqlNodeType.Or:
                case SqlNodeType.LessThan:
                case SqlNodeType.LessThanOrEqual:
                case SqlNodeType.GreaterThan:
                case SqlNodeType.GreaterThanOrEqual:
                case SqlNodeType.Equal:
                case SqlNodeType.NotEqual:
                    return this.VisitBinary((SqlBinary)node);
            }
            throw new Exception(string.Format("未处理的表达式类型！{0}",node.NodeType));
        }

        private SqlNode VisitDefault(SqlDefault node)
        {
            switch (node.Command)
            {
                case "Select":
                    return MakeSelect(node);
                case "SelectMany":
                    return VisitSelectMany(node);
                case "Where":
                    return VisitWhere(node);
                case "Table":
                    return VisitTable(node);
                case "Parameter":
                    return VisitParameter(node);
                case "MethodCall":
                    return MakeMethod(node);
                case "MemberAccess":
                    return MakeMember(node);
                case "Constant":
                    return new SqlValue(node.Value, node.Expression);
                case "Binary":
                    return MakeBinary(node);
                case "New":
                    return MakeNew(node);
                case "Unary":
                    return Visit(node.SubNodes[0]);
                case "Join":
                    return VisitJoin(node);
            }

            throw new Exception(string.Format("未处理的表达式类型！{0}", node.Command));
        }

        private SqlNode VisitJoin(SqlDefault node)
        {
            return node;
        }

        private SqlLambda MakeLambda(SqlDefault node,SqlAliasRef[] alias)
        {
            dic = new Dictionary<ParameterExpression, SqlAliasRef>();
            for (int i = 0; i < ((LambdaExpression)node.Expression).Parameters.Count; i++)
            {
                ParameterExpression param = ((LambdaExpression)node.Expression).Parameters[i];
                SqlAliasRef aliasRef = alias[i];
                dic.Add(param, aliasRef);
            }
            SqlLambda lambda = new SqlLambda(node.Expression);
            lambda.Body = Visit(node.SubNodes[0]);
            dic = null;
            return lambda;
        }

        private SqlMember MakeMember(SqlDefault node)
        {
            SqlMember member = new SqlMember(node.Expression);
            member.Object = (SqlExpression)Visit(node.SubNodes[0]);
            member.Member = node.Value.ToString();
            return member;
        }

        private SqlMethod MakeMethod(SqlDefault node)
        {
            SqlMethod method = new SqlMethod(node.Expression);
            foreach (var item in node.SubNodes)
            {
                method.Arguments.Add((SqlExpression)Visit(item));
            }
            method.Method = node.Value.ToString();
            return method;
        }

        private SqlNode VisitParameter(SqlDefault node)
        {
            if (dic.ContainsKey((ParameterExpression)node.Expression))
                return dic[(ParameterExpression)node.Expression];
            throw new Exception("参数不正确");
        }

        private SqlTable VisitTable(SqlDefault node)
        {
            return new SqlTable((ITable)node.Value, node.Expression);
        }

        private SqlBinary MakeBinary(SqlDefault node)
        {
            SqlExpression left = (SqlExpression)Visit(node.SubNodes[0]);
            SqlExpression right = (SqlExpression)Visit(node.SubNodes[1]);

            switch (node.Value.ToString())
            {
                case "Add":
                    return new SqlAdd(left, right, node.Expression);
                case "Subtract":
                    return new SqlSubtract(left, right, node.Expression);
                case "Multiply":
                    return new SqlMultiply(left, right, node.Expression);
                case "Divide":
                    return new SqlDivide(left, right, node.Expression);
                case "Modulo":
                    return new SqlModulo(left, right, node.Expression);
                case "AndAlso":
                    return new SqlAnd(left, right, node.Expression);
                case "OrElse":
                    return new SqlOr(left, right, node.Expression);
                case "LessThan":
                    return new SqlLessThan(left, right, node.Expression);
                case "LessThanOrEqual":
                    return new SqlLessThanOrEqual(left, right, node.Expression);
                case "GreaterThan":
                    return new SqlGreaterThan(left, right, node.Expression);
                case "GreaterThanOrEqual":
                    return new SqlGreaterThanOrEqual(left, right, node.Expression);
                case "Equal":
                    return new SqlEqual(left, right, node.Expression);
                case "NotEqual":
                    return new SqlNotEqual(left, right, node.Expression);
            }
            throw new Exception("未处理的二元运算符");
        }

        private SqlSelect MakeSelect(SqlDefault node)
        {
            SqlSelect select = new SqlSelect(node.Expression);
            SqlSource source = (SqlSource)Visit(node.SubNodes[0]);
            SqlAlias alias = MakeAlias(source);
            SqlAliasRef aliasRef = new SqlAliasRef(null) { Alias = alias.Alias };
            SqlLambda lambda = MakeLambda((SqlDefault)node.SubNodes[1], new SqlAliasRef[] { aliasRef });
            select.From = alias;
            select.Row = MakeRow(lambda);
            return select;
        }

        private SqlNode VisitSelectMany(SqlDefault node)
        {
            SqlSelectMany selectMany = new SqlSelectMany(node.Expression);

            SqlSource source1 = (SqlSource)Visit(node.SubNodes[0]);
            SqlAlias alias1 = MakeAlias(source1);
            SqlAliasRef aliasRef1 = new SqlAliasRef(null) { Alias = alias1.Alias };
            
            SqlLambda lamdb1 = MakeLambda((SqlDefault)node.SubNodes[1], new SqlAliasRef[]{aliasRef1});

            SqlSource source2 = MakeSource(lamdb1);
            SqlAlias alias2 = MakeAlias(source2);
            SqlAliasRef aliasRef2 = new SqlAliasRef(null) { Alias = alias2.Alias };

            SqlRow row;
            if (node.SubNodes.Count > 1)
            {
                SqlLambda lambda = MakeLambda((SqlDefault)node.SubNodes[2], new SqlAliasRef[] { aliasRef1, aliasRef2 });
                row = MakeRow(lambda);
            }
            else
            {
                row = new SqlRow(lamdb1.Expression);
                row.Items.Add(aliasRef2);
            }
            selectMany.From1 = alias1;
            selectMany.From2 = alias2;
            selectMany.Row = row;
            return selectMany;
        }

        private SqlAlias MakeAlias(SqlSource source)
        {
            SqlAlias alias = new SqlAlias(source.Expression);
            alias.Source = source;
            alias.Alias = GetAliasName();
            return alias;
        }

        Dictionary<ParameterExpression, SqlAliasRef> dic;
        private SqlRow MakeRow(SqlLambda lambda)
        {
            if(lambda.Body is SqlRow)
                return (SqlRow)lambda.Body;
            SqlRow row = new SqlRow(lambda.Expression);
            row.Items.Add((SqlExpression)lambda.Body);
            return row;
        }

        private SqlRow MakeNew(SqlDefault node)
        {
            SqlRow row = new SqlRow(node.Expression);
            foreach (var item in node.SubNodes)
                row.Items.Add((SqlExpression)Visit(item));
            return row;
        }

        private SqlSource MakeSource(SqlLambda lambda)
        {
            return (SqlSource)lambda.Body;
        }

        private SqlExpression MakeConditon(SqlLambda lamdba)
        {
            return (SqlExpression)lamdba.Body;
        }

        private SqlNode VisitWhere(SqlDefault node)
        {
            SqlWhere where = new SqlWhere(node.Expression);
            SqlSource source = (SqlSource)Visit(node.SubNodes[0]);
            SqlAlias alias = MakeAlias(source);
            SqlAliasRef aliasRef = new SqlAliasRef(source.Expression);
            aliasRef.Alias = alias.Alias;

            SqlLambda condition = MakeLambda((SqlDefault)node.SubNodes[1], new SqlAliasRef[] { aliasRef });

            where.From = alias;
            where.Condition = MakeConditon(condition);
            return where;
        }

        private SqlNode VisitUnary(SqlUnary sqlUnary)
        {
            return sqlUnary;
        }

        private SqlNode VisitBinary(SqlBinary sqlBinary)
        {
            return sqlBinary;
        }
    }
}
